package com.sunday.common.core.lambda.toolkit;

import com.sunday.common.core.lambda.toolkit.support.LambdaMeta;
import com.sunday.common.core.lambda.toolkit.support.SFunction;
import com.sunday.common.core.lambda.toolkit.support.SerializedLambda;
import lombok.extern.slf4j.Slf4j;

import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Lambda 解析工具类
 *
 * @author zsy
 * @see com.baomidou.mybatisplus.core.toolkit.LambdaUtils
 * @since 2022/10/13
 */
@Slf4j
public final class LambdaUtils {

    /**
     * SerializedLambda 反序列化缓存
     * <p>
     * Java弱引用(WeakReference)
     * 在Java里, 当一个对象被创建时, 它被放在内存堆里. 当GC运行的时候, 如果发现没有任何引用指向该对象, 该对象就会被回收以腾出内存空间. 或者换句话说, 一个对象被回收, 必须满足两个条件: 1)没有任何引用指向它 2)GC被运行
     * Java对于简单的情况, 如方法中的产生的局部对象，是不需要程序员来手动置空,  当调用它的方法执行完毕后, 指向它的引用会被GC回收。而复杂一点的情况，比如使用cache,因为cache的对象正是程序运行需要的, 那么只要程序正在运行, cache中的引用就不会被GC，那么随着cache中的引用越来越多, GC无法回收的对象也越来越多, 无法被自动回收，此时就必须有开发者来进行处理回收，显然也违背了java自动回收机制。
     * 对此，java中引入了弱引用(WeakReference)。
     * 当一个对象仅仅被weak reference指向, 而没有任何其他strong reference指向的时候, 如果GC运行, 那么这个对象就会被回收。如果存在强引用同时与之关联，则进行垃圾回收时也不会回收该对象。
     */
    private static final Map<Class<?>, WeakReference<LambdaMeta>> FUNC_CACHE = new ConcurrentHashMap<>();

    /**
     * function解析
     * 解析 lambda 表达式, 该方法只是调用了 {@link SerializedLambda#extract(java.io.Serializable)} 中的方法，在此基础上加了缓存。
     * 该缓存可能会在任意不定的时间被清除
     * <T>  类型，被调用的 Function 对象的目标类型
     *
     * @param func 需要解析的 lambda 对象
     * @return {@link LambdaMeta}
     */
    public static <T> LambdaMeta extract(SFunction<T, ?> func) {
        Class<?> clazz = func.getClass();
        return Optional.ofNullable(FUNC_CACHE.get(clazz))
                .map(WeakReference::get)
                .orElseGet(() -> {
                    LambdaMeta lambdaMeta = parse(func);
                    FUNC_CACHE.put(clazz, new WeakReference<>(lambdaMeta));
                    return lambdaMeta;
                });
    }


    /**
     * 参考 mybatis plus写法，存在一个问题，他舍弃了缓存，采用函数式变成最终形成了 java.lang.invoke.SerializedLambda
     * 这样就需要在维护一下接口与实现类
     *
     * @param func
     * @return {@link LambdaMeta}
     * @see com.baomidou.mybatisplus.core.toolkit.LambdaUtils
     */
    public static <T> LambdaMeta parse(SFunction<T, ?> func) {
        // 1. 反射读取
        try {
            /**
             * 函数式编程拿到对应方法的getter方法对应的属性名称
             * Function<T,R>注解。且必须继承自Serializable注解（作用，必须实例化才能玩“writeReplace”）
             */
            Method method = func.getClass().getDeclaredMethod("writeReplace");
            method.setAccessible(true);
            java.lang.invoke.SerializedLambda lambda = (java.lang.invoke.SerializedLambda) method.invoke(func);
            return LambdaMeta.of(lambda.getImplMethodName(), lambda.getInstantiatedMethodType());
//            return (java.lang.invoke.SerializedLambda) method.invoke(func);
//            return (java.lang.invoke.SerializedLambda) ReflectionKit.setAccessible(method).invoke(func);
        } catch (Throwable e) {
            // 2. 反射失败使用序列化的方式读取
            SerializedLambda lambda = SerializedLambda.extract(func);
            return LambdaMeta.of(lambda.getImplMethodName(), lambda.getInstantiatedMethodType());
//            return SerializedLambda.extract(func);
        }
    }

}
